// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fbl/auto_call.h>
#include <lib/fzl/vmo-mapper.h>
#include <zircon/assert.h>

#include <utility>

namespace fzl {

zx_status_t VmoMapper::CreateAndMap(uint64_t size, zx_vm_option_t map_flags,
                                    fbl::RefPtr<VmarManager> vmar_manager, zx::vmo* vmo_out,
                                    zx_rights_t vmo_rights, uint32_t cache_policy,
                                    uint32_t vmo_options) {
  if (size == 0) {
    return ZX_ERR_INVALID_ARGS;
  }

  zx_status_t res = CheckReadyToMap(vmar_manager);
  if (res != ZX_OK) {
    return res;
  }

  zx::vmo vmo;
  zx_status_t ret = zx::vmo::create(size, vmo_options, &vmo);
  if (ret != ZX_OK) {
    return ret;
  }

  if (cache_policy != 0) {
    ret = vmo.set_cache_policy(cache_policy);
    if (ret != ZX_OK) {
      return ret;
    }
  }

  ret = InternalMap(vmo, 0, size, map_flags, std::move(vmar_manager));
  if (ret != ZX_OK) {
    return ret;
  }

  if (vmo_out) {
    if (vmo_rights != ZX_RIGHT_SAME_RIGHTS) {
      ret = vmo.replace(vmo_rights, &vmo);
      if (ret != ZX_OK) {
        Unmap();
        return ret;
      }
    }

    *vmo_out = std::move(vmo);
  }

  return ZX_OK;
}

zx_status_t VmoMapper::Map(const zx::vmo& vmo, uint64_t offset, uint64_t size,
                           zx_vm_option_t map_options, fbl::RefPtr<VmarManager> vmar_manager) {
  zx_status_t res;

  if (!vmo.is_valid()) {
    return ZX_ERR_INVALID_ARGS;
  }

  res = CheckReadyToMap(vmar_manager);
  if (res != ZX_OK) {
    return res;
  }

  uint64_t vmo_size;
  res = vmo.get_size(&vmo_size);
  if (res != ZX_OK) {
    return res;
  }

  uint64_t end_addr;
  if (add_overflow(size, offset, &end_addr) || end_addr > vmo_size) {
    return ZX_ERR_OUT_OF_RANGE;
  }

  if (!size) {
    size = vmo_size - offset;
  }

  return InternalMap(vmo, offset, size, map_options, vmar_manager);
}

void VmoMapper::Unmap() {
  if (start() != nullptr) {
    ZX_DEBUG_ASSERT(size_ != 0);
    zx_handle_t vmar_handle =
        (vmar_manager_ == nullptr) ? zx::vmar::root_self()->get() : vmar_manager_->vmar().get();

    __UNUSED zx_status_t res;
    res = zx_vmar_unmap(vmar_handle, start_, size_);
    ZX_DEBUG_ASSERT(res == ZX_OK);
  }

  vmar_manager_.reset();
  start_ = 0;
  size_ = 0;
}

zx_status_t VmoMapper::CheckReadyToMap(const fbl::RefPtr<VmarManager>& vmar_manager) {
  if (start_ != 0) {
    return ZX_ERR_BAD_STATE;
  }

  if ((vmar_manager != nullptr) && !vmar_manager->vmar().is_valid()) {
    return ZX_ERR_INVALID_ARGS;
  }

  return ZX_OK;
}

zx_status_t VmoMapper::InternalMap(const zx::vmo& vmo, uint64_t offset, uint64_t size,
                                   zx_vm_option_t map_options,
                                   fbl::RefPtr<VmarManager> vmar_manager) {
  ZX_DEBUG_ASSERT(vmo.is_valid());
  ZX_DEBUG_ASSERT(start() == nullptr);
  ZX_DEBUG_ASSERT(size_ == 0);
  ZX_DEBUG_ASSERT(vmar_manager_ == nullptr);

  zx_handle_t vmar_handle =
      (vmar_manager == nullptr) ? zx::vmar::root_self()->get() : vmar_manager->vmar().get();

  zx_status_t res = zx_vmar_map(vmar_handle, map_options, 0, vmo.get(), offset, size, &start_);
  if (res != ZX_OK) {
    return res;
  }

  size_ = size;
  vmar_manager_ = std::move(vmar_manager);

  return ZX_OK;
}

}  // namespace fzl
